#pragma once

#include <iostream>
#include <queue>
#include <pthread.h>


//假设阻塞队列写完了，我们先写上层调用的逻辑
template <class T>
class BlockQueue
{
public:
    static const int _gmaxcap;//静态const成员，仅仅只能在类外被定义的时候初始化
public:
    BlockQueue(const int& maxcap = _gmaxcap)
        :_maxcap(maxcap)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_pcond, nullptr);
        pthread_cond_init(&_ccond, nullptr);
    }

    void push(const T &in)//输入型参数:const T&
    {
        //进来先保证访问共享资源的安全性，所以要先加锁
        pthread_mutex_lock(&_mutex);
        //1.判断
        //细节3: 发生伪唤醒的情况，假设有10个线程在生产，消费者只有一个，消费数据之后broadcast式的唤醒所有生产者，但是你只消费了一个数据，此时在pthread_cond_wait
        //       返回之后生产者继续向后运行，10个线程在push，但空位置却没有那么多，所以push的时候极有可能会出错!!!
        //       所以每个生产线程醒来的时候，都应该先做一件事情，判断是否满足生产的条件，不满足就继续等待!
        while(is_full())//bug? if->while 必须是while，不能是if
        {
            //细节1: wait是在临界区里面等的，线程挂起的时候是抱着锁挂起的，所以其他线程此时就无法进入临界区进行数据生产，效率变低，生产厂商可不止一个啊！
            //      ---所以，pthread_cond_wait在被调用的时候，会以原子性的方式，将锁释放(函数内部会自动释放锁)，并将自己挂起
            //细节2: wait被唤醒的时候，还需要从pthread_cond_wait开始向下运行，但这不还是在临界区吗？没有锁怎么继续向后运行啊？
            //      ---所以，pthread_cond_wait该函数返回的时候，会自动的重新获取你传入的锁，继续在临界区中向后运行
            //         (即使没有立马获取到锁，也会竞争式的直到获取到锁才会返回，所以只要pthread_cond_wait返回，那么一定是持有锁式的返回！)
            pthread_cond_wait(&_pcond, &_mutex);//满的时候，让p去他自己的cond的wait队列等待去
        }
        //2.走到这里一定是没有满
        _q.push(in);//如果队列没满，那就push数据
        //3.走到这里，绝对能保证，阻塞队列里面有数据(至少有一个)
        //细节4: 这个函数可以放在临界区内部也可以放在临界区外部，这都是可以的!(放在外面就是先把锁释放，让其他生产者能竞争到这把锁，然后唤醒消费者)
        pthread_cond_signal(&_ccond);//唤醒消费者的条件变量中的等待线程，这里可以有一定的策略，low/high water水位线，大于1/3的时候才会去唤醒consumer

        pthread_mutex_unlock(&_mutex);
    }

    void pop(T *out)//输出型参数:T*    输入输出型参数:T&
    {
        //进来先保证访问共享资源的安全性，所以要先加锁，拿数据不也是访问共享资源嘛？    
        pthread_mutex_lock(&_mutex);//p和c用同一把锁，保证两者在访问数据时是互斥的，安全的
        //1.判断
        while(is_empty())//bug?
        {
            pthread_cond_wait(&_ccond, &_mutex);//如果是空的，让c去他自己的cond的wait队列等待去
        }
        //2.走到这里队列一定不为空，最少也有一个数据
        *out = _q.front();//拿出数据
        _q.pop();
        //3.走到这里，一定能保证，阻塞队列里面，一定有空的位置
        pthread_cond_signal(&_pcond);//将消费者进行唤醒。这里也可以设置一定策略，当元素低于2/3时候，我们再去生产

        pthread_mutex_unlock(&_mutex);
    }

    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_pcond); 
        pthread_cond_destroy(&_ccond);
    }
private:
    bool is_empty()
    {
        return _q.empty();
    }
    bool is_full()
    {
        
        return _q.size() == _maxcap;
    }
private:
    std::queue<T> _q;
    int _maxcap;//阻塞队列中元素的上限
    pthread_mutex_t _mutex;//这个阻塞队列是一个共享资源，为了保证共享资源的安全，我们需要一把互斥锁来保护这个临界资源
    pthread_cond_t _ccond;//空的时候让c去cond的wait队列
    pthread_cond_t _pcond;//满的时候让p去cond的wait队列
};

template <class T>
const int BlockQueue<T>::_gmaxcap = 500;


